package com.dyt.itool.parser;

import com.dyt.itool.generator.*;
import com.dyt.itool.model.IProperty;
import com.dyt.itool.model.Struct;
import com.dyt.itool.model.impl.BasicProperty;
import com.dyt.itool.model.impl.ListProperty;
import com.dyt.itool.model.impl.MapProperty;
import com.dyt.itool.util.ByteBuffer;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ExcelStructParser extends AbstractParser {

    private Logger logger = Logger.getLogger(ExcelStructParser.class);

    private List<Struct> structList = new ArrayList<Struct>();
    private Map<Struct, List<Struct>> structDataMap = new HashMap<Struct, List<Struct>>();

    public ExcelStructParser() {

    }

    public List<Struct> getStructList() {
        return structList;
    }

    public Map<Struct, List<Struct>> getStructDataMap() {
        return structDataMap;
    }

    /**
     * 按名称查询结构体
     * @param structName 结构体名称
     * @return 结构体
     */
    public Struct getStructByName( String structName ){
        for( Struct s : structList  ){
            if( s.getStructName().equals( structName ) ){
                return s;
            }
        }
        return null;
    }

    public void parseDir(String dirName) {
        File dir = new File(dirName);
        if (!dir.exists() || !dir.isDirectory()) {
            logger.debug("要解析的文件夹不存在");
            return;
        }

        File[] fileList = dir.listFiles();
        for (File file : fileList) {
            parseFile(file);
        }

    }

    public void parse(String filename) {

        parse(filename, false);
    }

    public void parse(String filename, boolean isDictMeta) {

        File file = new File(filename);

        parseFile(file, isDictMeta);
    }

    public void parseFile(File file) {
        parseFile(file, false);
    }

    public void parseFile(File file, boolean isDictMeta) {
        logger.info("parse file " + file.getName());
        if (!file.exists() || !file.isFile()) {
            logger.debug("要解析的文件不存在");
            return;
        }

        if ((file.getName().startsWith(".") || file.getName().startsWith("~"))
                && !(file.getName().startsWith("DictMeta") ^ isDictMeta)) {
            logger.debug("无法解析的文件名:" + file.getName());
            return;
        }

        if (!file.getName().endsWith(".xls")
                && !file.getName().endsWith(".xlsx")) {
            logger.debug("无法解析的文件名:" + file.getName());
            return;
        }

        Workbook wb = null;

        try {
            if (file.getName().endsWith("xls")) {
                wb = new HSSFWorkbook(new FileInputStream(file));
            } else if (file.getName().endsWith("xlsx")) {
                wb = new XSSFWorkbook(new FileInputStream(file));
            }
        } catch (Exception e) {
            e.printStackTrace();

            logger.debug(e.getMessage());
            return;
        }

        System.out.println( "开始解析文件:" +  file.getName() );

        String structName = file.getName().replace(".xlsx", "")
                .replace(".xls", "");
        Struct struct = new Struct(structName);
        structList.add(struct);
        List<Struct> dataList = new ArrayList<Struct>();
        structDataMap.put(struct, dataList);

        String sheetName = wb.getSheetName(0);
        if (sheetName != null && sheetName.indexOf("竖表") != -1) {
            parseDataStructVertical(wb, struct);
            parseDataLinesVertical(wb, struct, dataList);
        } else {
            parseDataStruct(wb, struct);
            parseDataLines(wb, struct, dataList);
        }

        System.out.println( "解析文件成功:" +  file.getName() );
    }

    /*
    public void parseFile(String path, String name, List<Struct> dataList) {
        File file = new File(path);
        logger.info("parse file " + file.getName());
        if (!file.exists() || !file.isFile()) {
            logger.debug("要解析的文件不存在");
            return;
        }

        if (file.getName().startsWith(".") || file.getName().startsWith("~")) {
            logger.debug("无法解析的文件名:" + file.getName());
            return;
        }

        if (!file.getName().endsWith(".xls")
                && !file.getName().endsWith(".xlsx")) {
            logger.debug("无法解析的文件名:" + file.getName());
            return;
        }

        Workbook wb = null;

        try {
            if (file.getName().endsWith("xls")) {
                wb = new HSSFWorkbook(new FileInputStream(file));
            } else if (file.getName().endsWith("xlsx")) {
                wb = new XSSFWorkbook(new FileInputStream(file));
            }
        } catch (Exception e) {
            e.printStackTrace();

            logger.debug(e.getMessage());
            return;
        }
        parseData(wb, name, dataList);
    }
    */


    public void generateCodeOneByOne(Map<String, String> templateOutputMap,
                                     Map<String, Object> inputDataMap) {

        for (String templateFile : templateOutputMap.keySet()) {

            String outputFile = templateOutputMap.get(templateFile);

            try {

                Map<String, Object> dataMap = new HashMap<String, Object>();
                dataMap.put("cutil", new CHelper());
                dataMap.put("javautil", new JavaHelper());
                dataMap.put("luautil", new LuaHelper());

                // input key，value
                if (inputDataMap != null) {
                    for (Map.Entry<String, Object> entry : inputDataMap
                            .entrySet()) {
                        dataMap.put(entry.getKey(), entry.getValue());
                    }
                }

                for (Struct struct : structList) {

                    dataMap.put("model", struct);
                    String outFile = outputFile.replace("?",
                            struct.getStructName() + "Dict");
                    VelocityUtils.generateFromVelocity(templateFile, outFile,
                            dataMap);

                    Writer standOut = new OutputStreamWriter(System.out);
                    VelocityUtils.generateFromVelocity(templateFile, standOut,
                            dataMap);
                    standOut.flush();

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    public void generateCode(Map<String, String> templateOutputMap,
                             Map<String, Object> inputDataMap) {

        for (String templateFile : templateOutputMap.keySet()) {

            String outputFile = templateOutputMap.get(templateFile);

            try {

                Map<String, Object> dataMap = new HashMap<String, Object>();
                dataMap.put("modelList", structList);
                dataMap.put("cutil", new CHelper());
                dataMap.put("javautil", new JavaHelper());
                dataMap.put("luautil", new LuaHelper());
                dataMap.put("typescriptutil", new TypeScriptHelper());

                // input key，value
                if (inputDataMap != null) {
                    for (Map.Entry<String, Object> entry : inputDataMap
                            .entrySet()) {
                        dataMap.put(entry.getKey(), entry.getValue());
                    }
                }

                VelocityUtils.generateFromVelocity(templateFile, outputFile,
                        dataMap);

                Writer standOut = new OutputStreamWriter(System.out);
                VelocityUtils.generateFromVelocity(templateFile, standOut,
                        dataMap);
                standOut.flush();

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void generateBin(String outputDir, Map<String, Object> inputDataMap) {

        File dir = new File(outputDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        MetaHelper metaHelper = null;
        if (inputDataMap != null) {
            metaHelper = (MetaHelper) inputDataMap.get("metahelper");
        }

        for (Struct struct : structList) {

            if (metaHelper != null && metaHelper.containsType(struct.getStructName())) {
                continue;
            }

            List<Struct> dataList = structDataMap.get(struct);

            ByteBuffer buffer = new ByteBuffer();
            buffer.writeUTF8(struct.getMd5());
            buffer.writeInt(dataList.size());
            for (Struct dataLine : dataList) {
                writeStruct(buffer, dataLine);
            }

            try {

                FileOutputStream fos = new FileOutputStream(new File(dir,
                        struct.getStructName() + ".bin"));
                byte[] bytes = buffer.array();

                fos.write(bytes, 0, bytes.length);
                fos.flush();
                fos.close();

            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        if (metaHelper != null) {

            for (Map.Entry<String, String> item : metaHelper.getDictNames().entrySet()) {

                List<Struct> dataList = new ArrayList<>();

                for (Struct struct : structList) {
                    if (!metaHelper.containsType(struct.getStructName())) {
                        continue;
                    }
                    dataList.addAll(structDataMap.get(struct));
                }

                ByteBuffer buffer = new ByteBuffer();
                buffer.writeInt(dataList.size());
                for (Struct dataLine : dataList) {
                    writeStruct(buffer, dataLine);
                }

                try {

                    FileOutputStream fos = new FileOutputStream(new File(dir,
                            item.getValue() + ".bin"));
                    byte[] bytes = buffer.array();

                    fos.write(bytes, 0, bytes.length);
                    fos.flush();
                    fos.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    private void writeStruct(ByteBuffer buffer, Struct struct) {
        try{
            for (int i = 0; i < struct.getPropertyList().size(); i++) {
                writeProperty(buffer, struct.getPropertyList().get(i));
            }
        }catch (Exception e){
            logger.error(String.format("处理 %s 时出现异常:",struct.getStructName()),e);
            System.exit(0);
        }
    }

    private void writeProperty(ByteBuffer buffer, IProperty property) {

        if (property.getTypeName().toLowerCase().equals(IProperty.BOOL)) {
            buffer.writeBool(Boolean.parseBoolean(property.getValue()
                    .toString()));
        } else if (property.getTypeName().toLowerCase().equals(IProperty.BYTE)) {
            byte value = 0;

            if (!property.getValue().toString().trim().equals("")) {
                value = (byte) Math.round(Double.valueOf(property
                        .getValue().toString()));
            }
            buffer.writeByte(value);
        } else if (property.getTypeName().toLowerCase().equals(IProperty.SHORT)) {
            short value = 0;

            if (!property.getValue().toString().trim().equals("")) {
                value = (short) Math.round(Double.valueOf(property
                        .getValue().toString()));
            }
            buffer.writeShort(value);
        } else if (property.getTypeName().toLowerCase().equals(IProperty.INT)) {
            int value = 0;

            if (!property.getValue().toString().trim().equals("")) {
                value = (int) Math.round(Double.valueOf(property.getValue()
                        .toString()));
            }
            buffer.writeInt(value);
        } else if (property.getTypeName().toLowerCase().equals(IProperty.LONG)) {

            buffer.writeLong(Math.round(Double.valueOf(property.getValue()
                    .toString())));

        } else if (property.getTypeName().toLowerCase()
                .equalsIgnoreCase(IProperty.FLOAT)) {

            float value = 0.0f;

            if (!property.getValue().toString().trim().equals("")) {
                value = Double.valueOf(property.getValue().toString())
                        .floatValue();
            }

            buffer.writeFloat(value);
        } else {
            buffer.writeUTF8(property.getValue().toString());
        }

    }

    /*
    private void parseData(Workbook wb, String name, List<Struct> dataLines) {
        Sheet sheet = wb.getSheetAt(0);
        int rowSize = sheet.getPhysicalNumberOfRows();

        for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {
            Row row = sheet.getRow(rowIndex);
            if (row == null)
                continue;
            int columnSize = row.getPhysicalNumberOfCells();

            Struct dataModel = new Struct(name);

            for (int columnIndex = 0; columnIndex < columnSize; columnIndex++) {
                String value = getStringCellValue(row.getCell(columnIndex));
                IProperty prop = new BasicProperty("", "", value);
                dataModel.getPropertyList().add(prop);
            }
            dataLines.add(dataModel);
        }
    }
     */

    private boolean canAcceptColumn( String columnName ){
        // #都不读, $ 客户端不读， %服务器不读
    	return columnName != null && ( !columnName.startsWith( "#"  ) ) && ( !columnName.startsWith( "$"  ) );
    }

    private void parseDataStruct(Workbook wb, Struct dataStruct) {
        Sheet sheet = wb.getSheetAt(0);
        Row rowType = sheet.getRow(0);
        Row rowName = sheet.getRow(1);
        int columnSize = rowType.getPhysicalNumberOfCells();

        for (int column = 0; column < columnSize; column++) {
            String type = getStringCellValue(rowType.getCell(column));
            if(type.trim().length()==0){
                break;
            }
            String name = getStringCellValue(rowName.getCell(column));

            if( canAcceptColumn( name ) ){
            	IProperty property = parseProperty( type, name );
            	dataStruct.addProperty(property);
            }

        }
    }



    private void parseDataLines(Workbook wb, Struct dataStruct,
                                List<Struct> dataLines) {
        Sheet sheet = wb.getSheetAt(0);
        Row rowType = sheet.getRow(0);
        Row rowName = sheet.getRow(1);
        int rowSize = sheet.getPhysicalNumberOfRows();
        int columnSize = rowType.getPhysicalNumberOfCells();

        for (int rowIndex = 3; rowIndex < rowSize; rowIndex++) {
            Row row = sheet.getRow(rowIndex);
            if (row == null)
                continue;

            //如果本行第一个单元格无内容,跳过这行,
            //目前只在横表发现此问题,因此仅在横表中解决
            if (getStringCellValue( row.getCell(0)).trim().length()==0)
                continue;

            Struct dataModel = dataStruct.clone();

            int propIndex = 0;
            for (int columnIndex = 0; columnIndex < columnSize; columnIndex++) {
                
                try {
                    String columnName = getStringCellValue(rowName.getCell(columnIndex));
                    String columnValue = getStringCellValue(row.getCell(columnIndex));

                    if (canAcceptColumn(columnName)) {
                        IProperty prop = dataModel.getPropertyList().get(propIndex);
                        setProperty(prop, columnValue);
                        propIndex++;
                    }
                } catch (Exception e) {
                    logger.info("当前处理的列" + columnIndex);
                    throw e;
                }

            }

            dataLines.add(dataModel);
        }

    }


    private void parseDataStructVertical(Workbook wb, Struct dataStruct) {

        Sheet sheet = wb.getSheetAt(0);
        int rowSize = sheet.getPhysicalNumberOfRows();

        for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {
            Row row = sheet.getRow(rowIndex);

            String type = this.basicTypeMap.get( getStringCellValue(row.getCell(0)) );
            if(type.trim().length()==0){
                break;
            }
            String name = getStringCellValue(row.getCell(1));

        	if( canAcceptColumn( name ) ){
	            IProperty property = parseProperty( type , name);
	            dataStruct.addProperty(property);
        	}
        }
    }

    private void parseDataLinesVertical(Workbook wb, Struct dataStruct,
                                        List<Struct> dataLines) {

        Sheet sheet = wb.getSheetAt(0);
        Row rowType = sheet.getRow(0);
        int columnSize = rowType.getPhysicalNumberOfCells();

        for (int columnIndex = 3; columnIndex < columnSize; columnIndex++) {

            Struct dataModel = dataStruct.clone();

            for (int rowIndex = 0; rowIndex < dataModel.getPropertyList()
                    .size(); rowIndex++) {

                Row row = sheet.getRow(rowIndex);
                if (row == null)
                    continue;

                IProperty prop = dataModel.getPropertyList().get(rowIndex);

                setProperty(prop, getStringCellValue(row.getCell(columnIndex)));

            }

            dataLines.add(dataModel);
        }

    }

    private IProperty parseProperty(String typeName, String varName) {

        typeName = typeName.trim();
        varName = varName.trim();
        if( varName.startsWith( "%" ) ){
            varName = varName.substring( 1 );
        }

        // list中只能放基本数据类型
        if (typeName.toLowerCase().startsWith("list")) {
            String elementType = typeName.replace("list<", "").replace(">", "")
                    .trim();
            return new ListProperty("list", varName, new BasicProperty(
                    elementType, elementType));
        }

        // map中只能放基本数据类型
        if (typeName.toLowerCase().startsWith("map")) {
            String[] keyValue = typeName.replace("map<", "").replace(">", "")
                    .trim().split(",");
            String key = keyValue[0].trim();
            String value = keyValue[1].trim();

            key = this.basicTypeMap.get( key );
            value = this.basicTypeMap.get( value );

            return new MapProperty("map", varName, new BasicProperty( key, key),
                    new BasicProperty(value, value));
        }

        if (basicTypeMap.containsKey(typeName)) {
            return new BasicProperty( this.basicTypeMap.get( typeName ), varName);
        }

        return null;
    }

    private void setProperty(IProperty prop, String value) {
        if (prop == null)
            return;

        prop.setValue(value);
    }

    private String getStringCellValue(Cell cell) {

        if (cell == null) {
            return "";
        }

        String strCell = "";
        switch (cell.getCellType()) {
            case HSSFCell.CELL_TYPE_STRING:
                strCell = cell.getStringCellValue();
                break;
            case HSSFCell.CELL_TYPE_NUMERIC:
                strCell = String.valueOf(cell.getNumericCellValue());
                break;
            case HSSFCell.CELL_TYPE_BOOLEAN:
                strCell = String.valueOf(cell.getBooleanCellValue());
                break;
            case HSSFCell.CELL_TYPE_BLANK:
                strCell = "";
                break;
            case HSSFCell.CELL_TYPE_FORMULA:
                try {
                    strCell = String.valueOf(cell.getNumericCellValue());
                } catch (IllegalStateException e) {
                    strCell = String.valueOf(cell.getRichStringCellValue());
                }
                break;
            default:
                strCell = "";
                break;
        }
        if (strCell.equals("") || strCell == null) {
            return "";
        }

        return strCell.trim();
    }

}
